﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using Microsoft.WindowsAzure.Accelerator.Configuration.Definition;
using Microsoft.WindowsAzure.Accelerator.Diagnostics;
using Environment = System.Environment;

namespace Microsoft.WindowsAzure.Accelerator.Configuration
{
    /// <summary>
    /// Manages settings from the Azure service definition (.csdef), service configuration (.cscfg),
    /// and service accelerator configuration (.accfg) files. (i|rdm)
    /// </summary>
    public class Settings
    {
        private const String _DefaultServiceConfigurationFile = "ServiceConfiguration.cscfg";
        private const String _DefaultServiceDefinitionFile = "ServiceDefinition.csdef";
        private const String _DefaultServiceAcceleratorFile = "ServiceAccelerator.accfg"; 
        private readonly XNamespace _cns = "http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration";
        private readonly XNamespace _dns = "http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition";
        private readonly XNamespace _ans = "http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceAcceleratorConfiguration";
        
        private ServiceBusConnection _serviceBusConnection;
        private CloudStorageAccount _cloudStorageAccount;
        private Path _serviceDefinitionFile;
        private Path _serviceConfigurationFile;
        private Path _serviceAcceleratorFile;
        private XElement _xServiceDefinition;
        private XElement _xServiceConfiguration;
        private XElement _xServiceAccelerator;
        private Dictionary<String, String> _configSettings;
        private ServiceDefinition _serviceDefinition;
        private ServiceConfiguration _serviceConfiguration;
        private ServiceAccelerator _serviceAccelerator;

        /// <summary>
        /// Initializes a new instance of the <see cref="Settings"/> class.
        /// </summary>
        public Settings() : this(_DefaultServiceDefinitionFile, _DefaultServiceConfigurationFile, _DefaultServiceAcceleratorFile) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="Settings"/> class.
        /// </summary>
        /// <param name="serviceDefinitionFile">The service definition file.</param>
        /// <param name="serviceConfigurationFile">The service configuration file.</param>
        /// <param name="serviceAcceleratorFile">The service accelerator configuration file.</param>
        public Settings(Path serviceDefinitionFile, Path serviceConfigurationFile, Path serviceAcceleratorFile)
        {
            ServiceDefinitionFile = serviceDefinitionFile;
            ServiceConfigurationFile = serviceConfigurationFile;
            ServiceAcceleratorFile = serviceAcceleratorFile;
        }

        /// <summary>
        /// Gets a value indicating whether this instance is hostable web core.
        /// </summary>
        public Boolean IsHostableWebCore
        {
            get { return false; } //XServiceDefinition.Descendants("Site").FirstOrDefault() == null; }
        }

        /// <summary>
        /// Gets the diagnostics service bus connection.
        /// </summary>
        public ServiceBusConnection ServiceBusConnection
        {
            get { return _serviceBusConnection ?? ( _serviceBusConnection = ServiceBusConnection.Parse(GetConfigurationSetting("DiagnosticsServiceBus"))); }
        }

        /// <summary>
        /// Gets the default cloud drive URI.
        /// </summary>
        public String DefaultCloudDriveUri
        {
            get { return GetConfigurationSetting("AcceleratorDrivePageBlobUri") ?? DefaultSettings.CloudDriveUri;  }
        }

        /// <summary>
        /// Gets the default local site path.
        /// </summary>
        /// <remarks>
        /// Matained for HWC backwards compatiblity only. Is not useful for a SDK 1.3 sites based configuration,
        /// which includes a native physicalDirectory attribute.
        /// </remarks>
        public String DefaultLocalSitePath
        {
            get { return GetConfigurationSetting("LocalSitePath"); }
        }

        /// <summary>
        /// Gets the name of the application.
        /// </summary>
        public String ApplicationName
        {
            get { return ServiceConfiguration.serviceName; }
        }

        /// <summary>
        /// Gets the cloud storage account.
        /// </summary>
        public CloudStorageAccount CloudStorageAccount
        {
            get { return _cloudStorageAccount ?? ( _cloudStorageAccount = CloudStorageAccount.Parse(GetConfigurationSetting("AcceleratorConnectionString"))); }
        }
        
        /// <summary>
        /// Gets the service definition class information from the .csdef file.
        /// </summary>
        public ServiceDefinition ServiceDefinition
        {
            get { return _serviceDefinition ?? (_serviceDefinition = ServiceDefinition.LoadFromFile(ServiceDefinitionFile)); }
        }

        /// <summary>
        /// Gets the service configuration class from the cscfg file.
        /// </summary>
        public ServiceConfiguration ServiceConfiguration
        {
            get { return _serviceConfiguration ?? (_serviceConfiguration = ServiceConfiguration.LoadFromFile(ServiceConfigurationFile)); }
        }

        /// <summary>
        /// Gets the service accelerator configuration class from the accfg file.
        /// </summary>
        public ServiceAccelerator ServiceAccelerator
        {
            get { return _serviceAccelerator ?? (_serviceAccelerator = ServiceAccelerator.LoadFromFile(ServiceAcceleratorFile)); }
        }

        #region | XML

        /// <summary>
        /// Gets all 'Site' service definition elements defined in all roles.
        /// </summary>
        public List<XElement> XSites
        {
            get { return XServiceDefinition.Descendants(_dns + "Site").ToList(); }
        }

        /// <summary>
        /// Gets the service configuration setting elements.
        /// </summary>
        public Dictionary<String, String> ConfigurationSettings
        {
            get { return _configSettings ?? (_configSettings = GetRoleSettings(String.Empty)); }
            // XServiceConfiguration.Descendants(_cns + "Setting").ToDictionary(e => (String)e.Attribute("name"), e => (String)e.Attribute("value"))); }
        }

        /// <summary>
        /// Gets the service definition XML from the csdef file.
        /// </summary>
        public XElement XServiceDefinition
        {
            get { return _xServiceDefinition ?? (_xServiceDefinition = XDocument.Load(ServiceDefinitionFile).Element(_dns + "ServiceDefinition")); }
        }

        /// <summary>
        /// Gets the service configuration XML from the cscfg file.
        /// </summary>
        public XElement XServiceConfiguration
        {
            get { return _xServiceConfiguration ?? (_xServiceConfiguration = XDocument.Load(ServiceConfigurationFile).Element(_cns + "ServiceConfiguration")); }
        }

        /// <summary>
        /// Gets the service configuration XML from the cscfg file.
        /// </summary>
        public XElement XServiceAccelerator
        {
            get { return _xServiceAccelerator ?? (_xServiceAccelerator = XDocument.Load(ServiceAcceleratorFile).Element(_ans + "ServiceAccelerator")); }
        }

        #endregion
        #region | FILE RESOURCES

        /// <summary>
        /// Gets or sets the service definition (.csdef) file.
        /// </summary>
        public Path ServiceDefinitionFile
        {
            get { return _serviceDefinitionFile ?? (_serviceDefinitionFile = LocateConfigurationFile(_DefaultServiceDefinitionFile)); }
            set 
            { 
                if (!String.IsNullOrEmpty(value) && File.Exists(value))
                {
                    _serviceDefinitionFile = value;
                    _serviceDefinition = null;
                    _xServiceDefinition = null;
                }
            }
        }

        /// <summary>
        /// Gets or sets the service configuration (.cscfg) file.
        /// </summary>
        public Path ServiceConfigurationFile
        {
            get { return _serviceConfigurationFile ?? (_serviceConfigurationFile = LocateConfigurationFile(_DefaultServiceConfigurationFile)); }
            set 
            { 
                if (!String.IsNullOrEmpty(value) && File.Exists(value))
                {
                    _serviceConfigurationFile = value;
                    _serviceConfiguration = null;
                    _serviceConfigurationFile = null;
                }
            }
        }
        
        /// <summary>
        /// Gets or sets the service accelerator configuration (.accfg) file.
        /// </summary>
        public Path ServiceAcceleratorFile
        {
            get { return _serviceAcceleratorFile?? (_serviceAcceleratorFile= LocateConfigurationFile(_DefaultServiceAcceleratorFile)); }
            set
            {
                if (!String.IsNullOrEmpty(value) && File.Exists(value))
                {
                    _serviceAcceleratorFile= value;
                    _xServiceAccelerator = null;
                }
            }
        }

        /// <summary>
        /// Locates a configuration file using a known filename. This method will attempt to locate the file
        /// in the following four locations:
        ///   1. current directory
        ///   2. parent of current directory
        ///   3. binary directory
        ///   4. parent of binary directory
        /// </summary>
        /// <param name="filename">The file name.</param>
        private static Path LocateConfigurationFile(String filename)
        {
            Path path;
            if (!File.Exists(path = Path.Combine(".", filename).GetFullPath()))
                if (!File.Exists(path = path.Protect(p => p.GetParentFolder().Combine(filename))))
                    if (!File.Exists(path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, filename)))
                        if (!File.Exists(path = path.Protect(p => p.GetParentFolder().Combine(filename))))
                            path = null;
            return path;
        }

        #endregion

        /// <summary>
        /// Gets the configuration setting.
        /// </summary>
        /// <param name="settingName">Name of the setting.</param>
        /// <returns></returns>
        public String GetConfigurationSetting(String settingName)
        {
            return GetRoleConfigurationSetting(settingName, String.Empty);
        }

        /// <summary>
        /// Gets the configuration setting.
        /// </summary>
        /// <param name="settingName">Name of the setting.</param>
        /// <param name="roleName">Name of the role.</param>
        /// <returns></returns>
        public String GetRoleConfigurationSetting(String settingName, String roleName)
        {
            return GetRoleSettings(roleName).OnValid(rs => rs.ContainsKey(settingName) ? rs[settingName] : null);
        }

        /// <summary>
        /// Gets the configuration setting elements for the specified role.
        /// </summary>
        /// <param name="roleName">Name of the role.</param>
        /// <returns>List of setting elements.</returns>
        public Dictionary<String,String> GetRoleSettings(String roleName)
        {
            XElement scope = (String.IsNullOrEmpty(roleName))
                                 ? XServiceConfiguration
                                 : //i|String query = String.Format("serviceAccelerator/application[@name=\"{0}\"]" 
                             (from r in XServiceConfiguration.Elements()
                              where r.GetAttribute("value") == roleName
                              select r).FirstOrDefault();

            return scope.Descendants(_cns + "Setting").ToDictionary(e => (String)e.Attribute("name"), e => (String)e.Attribute("value")); 
        }

        /// <summary>
        /// Pushes the values of each of the service configuration settings into environment variables by the same name.  Used to
        /// single-source configuration information, while leveraging batch files for deployment.
        /// </summary>
        public void SetConfigEnvironment()
        {
            Trace.TraceInformation("Setting Environment Variables");
            foreach (var setting in ConfigurationSettings)
            {
                try
                {
                    String key = setting.Key;
                    String value = setting.Value;
                    Environment.SetEnvironmentVariable(key, value);
                    Trace.TraceInformation("\t{0:20} : {1}", key, value);
                    if (!String.IsNullOrEmpty(value) && key.ToLower().Contains("connectionstring"))
                    {
                        foreach (var kvp in setting.Value.SplitToDictionary<String, String>(new[] { ';' }, new[] { '=' }).Where(d => !String.IsNullOrEmpty(d.Value)))
                        {
                            //b| this needs to take into account role and setting name in a compound form.
                            Environment.SetEnvironmentVariable(String.Format("{0}_{1}", key, kvp.Key), kvp.Value);
                            Environment.SetEnvironmentVariable(kvp.Key, kvp.Value);
                            Trace.TraceInformation("\t{0:20} : {1}", kvp.Key, kvp.Value);
                        }
                    }
                }
                catch (Exception ex)
                {
                   Trace.TraceError(ex.ToString());
                }
            }
        }
    }
}